package math;

import java.util.ArrayList;
import java.util.Stack;

public class Evaluator {
	private EvalNode root;
	private Stack<Token> tokens;
	private FunctionTokenizer tokenizer;
	public Evaluator(){
		tokens = new Stack<Token>();
		tokenizer = new FunctionTokenizer();
	}
	public void insert(Stack<Token> tok){
		tokens = tok;
		if(root == null){
			root = new EvalNode(tokens.pop());
		}
		insert(root);
	}
	
	public void printTree(){
		printTree(root);
	}
	private void printTree(EvalNode e){
		if(e != null){
		System.out.print(e.tok);
		printTree(e.leftChild);
		printTree(e.rightChild);
		}
	}
	private void insert(EvalNode node){
		if(!tokens.isEmpty()){
			if(node.tok instanceof Operator){
				Operator o = (Operator)node.tok;
				if(o.isUnary()){
					node.leftChild = new EvalNode(tokens.pop());
					insert(node.leftChild);
				}else{
					node.leftChild = new EvalNode(tokens.pop());
					insert(node.leftChild);
					node.rightChild = new EvalNode(tokens.pop());
					insert(node.rightChild);
				}
			}
		}
	}
	
	public double evaluate(String func,double x){
		tokenizer.tokenize(func ,x);
		Stack<Token> s = postfix(tokenizer.tokens);
		insert(s);		
		return evaluate(root);
	}
	private double evaluate(EvalNode n){
		if(n == null){
			return 0;
		} else if(n.tok instanceof Operand){
			Operand o = (Operand) n.tok;
			return o.value;
		}else if(n.tok instanceof Operator){
			Operator o = (Operator)n.tok;
			if(o.isUnary()){
				double op = evaluate(n.leftChild);
				return operate(o.getOperator(),op);
			}else{
				double op1 = evaluate(n.leftChild);
				double op2 = evaluate(n.rightChild);
				return operate(o.getOperator(),op1,op2);
			}
		}else{
			return -1;
		}
	}
	
	private double operate(char op,double val){
		if(op == Operator.SUB){
			return val*-1;
		}else if(op == Operator.FAC){
			return MathXT.factorial(val);
		}else{
			return 0;
		}
	}
	private double operate(char op, double val1,double val2){
		switch(op){
		case Operator.ADD:
			return val1+val2;
		case Operator.DIV:
			return val1/val2;
		case Operator.MULT:
			return val1*val2;
		case Operator.POW:
			return Math.pow(val1, val2);
		case Operator.SUB:
			return val1-val2;
		default:
			return 0;
		}
	}
	public Stack<Token> postfix(ArrayList<Token> tokens){
		Stack<Token> returnStack = new Stack<Token>();
		Stack<Operator> opStack = new Stack<Operator>();
		int i = 0;
		while(i<tokens.size()){
			Token t = tokens.get(i);
			if(t instanceof Operand){
				returnStack.push(t);
			}else if (t instanceof Operator){
				Operator o = (Operator)t;
				if(opStack.isEmpty() && o.getOperator() != Operator.RPAR){
					opStack.push(o);
				}else if(o.getOperator() == Operator.RPAR){
					while(!opStack.isEmpty()){
						if(opStack.peek().getOperator()== Operator.LPAR){
							opStack.pop();
							break;
						}
						returnStack.push(opStack.pop());
					}
				}else if (opStack.peek().getPriorety() >= o.getPriorety()){
					returnStack.push(opStack.pop());
					i--;
				}else if (opStack.peek().getPriorety() < o.getPriorety()){
					opStack.push(o);
				}
			}
			i++;
		}
		
		while(!opStack.isEmpty()){
			returnStack.push(opStack.pop());
		}
		return returnStack;
	}
	 class EvalNode{
		Token tok;
		EvalNode rightChild;
		EvalNode leftChild;
		EvalNode(Token t){
			tok = t;
		}
	}
	 
}
